﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Synchronization;
using Obra.Common;

namespace Obra.Sync
{
    public class FileSystemSyncServiceProvider
        : ISyncService
    {
        private readonly SyncOptions syncOptions;

        private readonly IFileSystemService fileSystem;

        /// <summary>
        /// Interface for querying a file system.
        /// </summary>
        public IFileSystemService FileSystem
        {
            get { return fileSystem; }
        }

        private readonly string directory;

        /// <summary>
        /// Directory to synchronize media files from.
        /// </summary>
        public string Directory
        {
            get { return directory; }
        }

        /// <summary>
        /// Get the path to the media file from its metadata.
        /// </summary>
        /// <param name="mediaItemMetadata">
        /// Metadata for the media file.
        /// </param>
        private string GetMediaItemPath(
            MediaItemMetadata mediaItemMetadata)
        {
            // normalize the path for testing purposes
            return Path.Combine(
                this.Directory,
                mediaItemMetadata.Name.Replace('/', '\\'));
        }

        /// <summary>
        /// c-tor
        /// </summary>
        /// <param name="syncOptions">
        /// The options for this provider.
        /// </param>
        public FileSystemSyncServiceProvider(
            string directory,
            SyncOptions syncOptions,
            IFileSystemService fileSystem)
        {
            this.fileSystem = fileSystem;
            this.syncOptions = syncOptions;
            this.directory = directory;
        }

        #region ISyncService Members

        /// <summary>
        /// The name of this synchronization service.
        /// </summary>
        public string Name
        {
            get { return "FileSystemSyncServiceProvider"; }
        }

        /// <summary>
        /// The description of this synchronization service.
        /// </summary>
        public string Description
        {
            get { return String.Empty;  }
        }

        /// <summary>
        /// No-op, file system provider does not have to connect.
        /// </summary>
        public void Connect()
        {
        }

        /// <summary>
        /// No-op, file system provider does not have to disconnect.
        /// </summary>
        public void Disconnect()
        {
        }

        /// <summary>
        /// The synchronization options for this provider.
        /// </summary>
        public SyncOptions SyncOptions
        {
            get { return syncOptions; }
        }

        /// <summary>
        /// Convert a file name to a media name.
        /// </summary>
        /// <param name="name">
        /// Name of the file.
        /// </param>
        /// <remarks>
        /// <para>
        /// A file name is c:\users\user\Pictures\album1\image1.jpg, and the 
        /// media name is album1/image1.jpg.
        /// </para>
        /// </remarks>
        private string FileInfoNameToMediaName(
            string name)
        {
            return name.Remove(0, this.Directory.Length + 1).Replace("\\", "/");
        }

        /// <summary>
        /// Get a list of media files.
        /// </summary>
        /// <remarks><para>
        /// Only one level deep is considered when searching for media files.  
        /// The reason is simple, it is the way Picasa organizes a user's media,
        /// and the way photo web sites generally work.  There are two 
        /// possibilities, but only the first is considered valid.
        /// 
        ///   1. TopLevelDirectory/
        ///      ->  Album1/Image1.JPG
        ///      ->  Album1/Image2.JPG
        ///      ->  AlbumN/Image1.JPG
        ///   2. TopLevelDirectory/
        ///      -> Image1.JPG
        ///      -> ImageN.JPG
        /// </para></remarks>
        public List<MediaItemMetadata> EnumerateMediaFiles()
        {
            List<MediaItemMetadata> metadata = new List<MediaItemMetadata>();

            metadata.AddRange(
                this.FileSystem.GetMediaFiles(this.Directory).Select(x =>
                    new MediaItemMetadata()
                    {
                        Name = this.FileInfoNameToMediaName(x.FullName),
                        LastModifiedUtc = (ulong) x.LastWriteTimeUtc.Ticks,
                    }));

            return metadata;
        }

        /// <summary>
        /// Checksum a media file.
        /// </summary>
        /// <param name="mediaItemMetadata">
        /// Metadata of the media file to compute the checksum of.
        /// </param>
        /// <returns>
        /// String representation of the media file's checksum.
        /// </returns>
        public string ComputeChecksum(
            MediaItemMetadata mediaItemMetadata)
        {
            return this.FileSystem.ComputeChecksum(
                this.GetMediaItemPath(mediaItemMetadata),
                this.SyncOptions.ChecksumAlgorithm);
        }

        /// <summary>
        /// Delete a media file from this provider.
        /// </summary>
        /// <param name="mediaItemMetadata">
        /// Metadata for the media file to delete.
        /// </param>
        public void DeleteMediaFile(
            MediaItemMetadata mediaItemMetadata)
        {
            this.FileSystem.Delete(
                this.GetMediaItemPath(mediaItemMetadata));
        }

        /// <summary>
        /// Save a SyncItem -- write to disk, upload to SmugMug.
        /// </summary>
        /// <param name="syncItem">
        /// Source SyncItem to overwrite, may be null.
        /// </param>
        /// <param name="syncItemTransfer">
        /// SyncItem to transfer to this provider, and save.
        /// </param>
        /// <returns>
        /// True if the source and target SyncItem are identical.
        /// </returns>
        public void SaveChange(
            ISyncItemTransfer syncItemTransfer)
        {
            this.FileSystem.Write(
                this.GetMediaItemPath(syncItemTransfer.MediaItemMetadata),
                syncItemTransfer.Stream);
        }

        /// <summary>
        /// Load a SyncItem -- read from disk, download from SmugMug.
        /// </summary>
        /// <param name="syncItem">
        /// Object representing a transfer object.
        /// </param>
        public ISyncItemTransfer LoadChange(
            string mediaName)
        {
            MediaItemMetadata mediaItemMetadata = new MediaItemMetadata()
            {
                Name = mediaName,
            };

            ISyncItemTransfer syncItemTransfer = new SyncItemTransferLazyStream(
                mediaItemMetadata,
                this.FileSystem.LazyLoad(this.GetMediaItemPath(mediaItemMetadata)));

            return syncItemTransfer;
        }

        #endregion
    }
}
